use super::{
    err, err_value, get_dir, ok, throw_err, Base, Client, ECode, Lock, MapSv, Resp, VBase, VMap,
    Value,
};
use rust_xlsxwriter::*;
use serde_json::json;
use std::{
    fs,
    io::{Read, Write},
    path::Path,
};
use tracing::{instrument, warn};
use zip::write::SimpleFileOptions;

pub struct Esop;

impl Base for Esop {}

impl Esop {
    pub async fn do_cmd(
        &self,
        para: &Value,
        cmd: &str,
        tech: &str,
        client: &Client,
        lock: Lock,
    ) -> Resp {
        let data = para.ckobj()?;

        match cmd {
            "wesop" => self.write_esop(data, tech, client, lock).await,
            _ => throw_err!(ECode::NoServe, "no esop serve"),
        }
    }

    #[instrument(skip_all, name = "write_esop")]
    async fn write_esop(&self, data: &MapSv, tech: &str, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let line = data.ck_str("line")?;

        let path = format!("{}{}/tech/{}/tech/step.xlsx", get_dir(), line, tech);
        warn!("write file: {}", path);

        let zip_dir = Path::new(&path)
            .parent()
            .ok_or(err_value(ECode::FileRead, "path error"))?;
        warn!("zip dir: {}", zip_dir.display());

        let zip_path = zip_dir
            .parent()
            .ok_or(err_value(ECode::FileRead, "path error"))?
            .join("tech.zip");
        warn!("zip path: {}", zip_path.display());

        let w_row = self.get_esop(line, tech, client).await?;

        self.w_xlsx(&w_row, &path)
            .or_else(|e| err!(ECode::FileWrite, e))?;

        let file = fs::File::create(zip_path).or_else(|e| err!(ECode::FileWrite, e))?;
        let mut zip = zip::ZipWriter::new(file);

        let opt = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);

        let dir = fs::read_dir(zip_dir).or_else(|e| err!(ECode::FileWrite, e))?;
        let mut buffer = Vec::new();

        for entry in dir {
            let f = entry.or_else(|e| err!(ECode::FileWrite, e))?.path();

            if f.is_dir() {
                continue;
            }

            let name = f
                .as_path()
                .file_name()
                .ok_or(err_value(ECode::FileWrite, "file error"))?
                .to_str()
                .map(str::to_owned)
                .ok_or(err_value(ECode::FileWrite, "file error"))?;

            zip.start_file(name, opt)
                .or_else(|e| err!(ECode::FileWrite, e))?;

            let mut file = fs::File::open(f).or_else(|e| err!(ECode::FileWrite, e))?;

            file.read_to_end(&mut buffer)
                .or_else(|e| err!(ECode::FileWrite, e))?;

            zip.write_all(&buffer)
                .or_else(|e| err!(ECode::FileWrite, e))?;

            buffer.clear();
        } // for

        zip.finish().or_else(|e| err!(ECode::FileWrite, e))?;

        ok!()
    }

    fn w_xlsx(&self, data: &Vec<Vec<String>>, path: &str) -> Result<(), XlsxError> {
        let mut workbook = Workbook::new();

        let sheet = workbook.add_worksheet();

        for (row, row_data) in data.iter().enumerate() {
            for (col, item) in row_data.iter().enumerate() {
                sheet.write(row as u32, col as u16, item)?;
            }
        }

        workbook.save(path)?;

        Ok(())
    }

    async fn get_esop(
        &self,
        line: &str,
        tech: &str,
        client: &Client,
    ) -> Result<Vec<Vec<String>>, Value> {
        let cmd = json!({ "line": line });
        let body = client.cfgdb(&cmd, "field/list/esop").await?;
        let body = body.ck_array("data")?;

        let mut col = Vec::new();
        let mut head = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let sn = d.ck_str_empty_trim("sn")?;
            let name = d.ck_str_empty_trim("name")?;

            head.push(name.to_string());
            col.push(sn);
        }

        let cmd = json!({ "line": line, "k": tech });
        let body = client.cfgdb(&cmd, "esop/list").await?;
        let body = body.ck_obj("data")?;

        let mut w_row = vec![head];

        for (_, row) in body.iter() {
            let row = row.ckarray()?;

            for d in row.iter() {
                let d = d.ckobj()?;

                let mut tmp = Vec::new();

                for t in col.iter() {
                    let val = d.ck_val(t)?;
                    tmp.push(val.to_string());
                }

                w_row.push(tmp);
            }
        }

        if w_row.is_empty() {
            throw_err!(ECode::EmptyData, "no step data");
        }

        Ok(w_row)
    }
}
